<?php
/**
 * +------------------------------
 * Created by PhpStorm.
 * +------------------------------
 * User: xxx
 * +------------------------------
 * DateTime: 2019/12/12 9:22
 * +------------------------------
 */

namespace Core\lib;


use Swoole\Coroutine\Channel;
use Swoole\Timer;

/**
 * Redis 连接池 抽象类
 * Class RedisPool
 * @package Core\lib
 */
abstract class RedisPool
{

    /**
     * 最小连接数
     * @var int
     */
    private $min;

    /**
     * 最大连接数
     * @var int
     */
    private $max;

    /**
     * 存放连接对象
     * @var Channel
     */
    private $conns;

    /**
     * 当前所有连接数
     * @var
     */
    private $counts;

    /**
     * 连接空闲时间（秒）
     * @var int
     */
    private $idleTime = 10;

    /**
     * 创建DB对象（抽象方法）
     * @return mixed
     */
    abstract protected function newRedis();

    /**
     * DBPool constructor.
     * @param int $min
     * @param int $max
     * @param $idleTime
     */
    public function __construct($min = 5, $max = 10, $idleTime = 10)
    {

        $this->min = $min;
        $this->max = $max;
        $this->idleTime = $idleTime;

        $this->conns = new Channel($this->max);

        //构造方法直接初始化DB链接
        for ($i = 0; $i < $this->min; $i++) {
            $this->addRedisToPool(); //统一调用
        }

    }

    /**
     * @return mixed
     */
    public function getCounts()
    {
        return $this->counts;
    }

    /**
     * 初始化连接池 （根据最小连接数）
     */
    public function initPool()
    {
        //TODO：暂时停掉检查
        /*for ($i = 0; $i < $this->min; $i++) {
            $this->addDBToPool();
        }

        //定时器：，每两秒执行一次回收对象
        Timer::tick(2 * 1000, function () {
            $this->clearPool();
        });*/

    }

    /**
     * 取得连接
     * @return mixed
     */
    public function getConnection()
    {
        //如果为空则 加入一个新的db连接
        if ($this->conns->isEmpty()) {

            //当前连接数已经小于max，才新建连接
            if ($this->counts < $this->max) {
                $this->addRedisToPool();
                $getObject = $this->conns->pop();
            } else {
                //等待5秒返回
                $getObject = $this->conns->pop(5);
            }
        } else {
            $getObject = $this->conns->pop();
        }

        if ($getObject) {
            $getObject->usedTime = time();
        }

        return $getObject;
    }

    /**
     * 添加DB对象到连接池
     */
    public function addRedisToPool()
    {
        try {
            $this->counts++;

            $db = $this->newRedis();
            if (!$db) {
                throw new \Exception("Redis创建出错");
            }

            $dbObject = new \stdClass();
            $dbObject->usedTime = time();
            $dbObject->redis = $db;

            $this->conns->push($dbObject);
        } catch (\Exception $e) {
            $this->counts--;
        }

    }

    /**
     * 把conn放回连接池
     * @param $conn
     */
    public function close($conn)
    {
        if ($conn) {
            $this->conns->push($conn);
        }
    }

    /**
     * (定时器)定期清理回收DB对象
     */
    private function clearPool(): void
    {
        if ($this->conns->length() <= $this->min && $this->conns->length() < intval($this->max * 0.6)) {
            return;
        }

        //开始清理
        echo "开始清理......" . PHP_EOL;

        $dbbak = [];

        while (true) {

            //如果为空则退出
            if ($this->conns->isEmpty()) {
                break;
            }

            $obj = $this->conns->pop(0.1);
            //条件1 ： 链接数大于最小链接数
            //条件2 ： （当前时间-使用时间） 大于 超时时间   如：使用时间超过10秒 则 链接数量减1
            if ($this->counts > $this->min && (time() - $obj->usedTime) > $this->idleTime) {
                $this->counts--;
            } else {
                array_push($dbbak, $obj);
            }
        }

        //回收对象
        foreach ($dbbak as $db) {
            $this->conns->push($db);
        }

        //清理完成
        echo "当前连接数:" . $this->counts . PHP_EOL;

    }
}